//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#include <buola/python.h>

#include <buola/algorithm/string.h>
#include <buola/python/cpythonbox.h>
#include <buola/widgets/cmenubar.h>
#include <buola/text/ctextdoc.h>
#include <buola/gui/cmenu.h>
#include <buola/widgets/ctoolbar.h>
#include <buola/gui/caccelerator.h>
#include <buola/functors/predicates/char.h>

#include <buola/python/cpythonstdout.h>
#include <buola/python/cpythonhelp.h>
#include <buola/python/cpythonthread.h>

namespace buola { namespace python {

using namespace gui;
using namespace python;

bool CPythonBox::mInterrupted=false;

CPythonBox::CPythonBox()
    :   mRunning(false),
        mCtrlCAccel(nullptr)
{
    SetPossibleActions(CWindow::ACTION_CLOSE|CWindow::ACTION_DOCK|CWindow::ACTION_MINIMIZE);
}

CPythonBox::~CPythonBox()
{
}

void CPythonBox::SetStartupCommand(const std::wstring &pString)
{
    mStartupCommand=pString;
}

void CPythonBox::WriteLine(const std::wstring &pString)
{
    mView->Output(pString);
}

void CPythonBox::RunText(const std::wstring &pString,bool pEcho)
{
    if(isspace(pString[0])) //we have to unindent it
    {
        std::vector<std::wstring> lLines;

        split(pString,lLines,fn::equal_to('\n'));

        int lHowMuch=std::find_if(lLines[0].begin(),lLines[0].end(),!fn::is_space())-
                        lLines[0].begin();

        if(lHowMuch>=0) //if not, just go through to execute it as-is
        {
            bool lOK=true;

            for(int i=0;i<lLines.size();i++)
            {
                int lSpaces=std::find_if(lLines[i].begin(),lLines[i].end(),!fn::is_space())-
                                lLines[i].begin();
                if(lSpaces<0)   //only spaces
                {
                    if(lHowMuch>=lLines[i].length())
                        lLines[i].erase(0,lHowMuch);
                }
                else if(lSpaces<lHowMuch)   //less spaces than first line
                {
                    lOK=false;
                    break;
                }
                else
                {
                    lLines[i].erase(0,lHowMuch);
                }
            }

            if(lOK)
            {
                //make it into a new string and run
                RunText(boost::join(lLines,L"\n")+L'\n');
                return;
            }
            //fail through... python will find the error
        }
    }

    mPendingCommand.clear();

    if(mView->IsInteracting())
    {
        mView->CancelInteract();
        WriteLine(L"\n");
    }

    if(pEcho)
    {
        WriteLine(pString);
        WriteLine(L"\n");
    }

    python::CLock lPythonLock;
    YObject lCode=mCompileCommand.Call(YObject(pString),
                    YObject(L"<editor>"),YObject(L"exec"));

    if(lCode.IsNull())
    {
        PyErr_Print();
        PyErr_Clear();
        mView->Interact(L">>> ",0);
        mIndentLevel=0;
    }
    else if(lCode.IsNone())
    {
        WriteLine(L"*** incomplete code ***");
        mView->Interact(L">>> ",0);
        mIndentLevel=0;
    }
    else
    {
        mRunning=true;
        mRunningThread=new CThread(lCode);
        mRunningThread->eThreadFinished.Connect(&CPythonBox::OnThreadFinished,this);
        mRunningThread->Start();
    }
}

void CPythonBox::GetCompletionOptions(std::vector<std::wstring> &pList,
                CInteractiveTextView*,const std::wstring &pText)
{
    python::CLock lPythonLock;

    YModule lModule=python::import_module("rlcompleter");
    YClass lClass=lModule.GetAttribute("Completer");
    YObject lCompleter=lClass.NewInstance();

    int i=0;

    for(;;)
    {
        YObject lC=lCompleter.CallMethod("complete",YObject(pText),YObject(i++));
        if(lC.IsNone()) break;
        if(lC.IsNull())
        {
            PyErr_Clear();
            break;
        }
        std::wstring lNew=lC.ToWString();
        if(std::find(pList.begin(),pList.end(),lNew)==pList.end())
            pList.push_back(lNew);
    }
}

bool CPythonBox::PauseUntilInput(EPauseMode pMode)
{
    static condition_variable lCondVar;
    static mutex lCondMutex;
    static int lSomeone=0;
    static bool lThrow=false;

    bool lReturn=false;

    unique_lock<mutex> lLock(lCondMutex);

    if(pMode==PAUSE_WAIT)
    {
        lSomeone++;
        lCondVar.wait(lLock);
        lSomeone--;
        lReturn=lThrow;
        lThrow=false;
    }
    else if(pMode==PAUSE_ENTER)
    {
        if(lSomeone)
        {
            lCondVar.notify_one();
            lReturn=true;
        }
    }
    else if(pMode==PAUSE_CTRLC)
    {
        if(lSomeone)
        {
            lCondVar.notify_one();
            lReturn=true;
            lThrow=true;
        }
    }

    return lReturn;
}

void CPythonBox::OnCreate()
{
    SetTitle(L"python interpreter");

    Resize(CSize_d(400,300));

    mDoc=new CTextDoc;
    mDoc->Create();
    mView=new CInteractiveTextView;
    mView->Create(this);
    mView->SetDocument(mDoc);
    mView->eInteract.Connect(&CPythonBox::OnInteract,this,_1);
    mView->sEnterNotInteracting.Connect(&CPythonBox::OnEnterNotInteracting,this);
    mView->SetCompleter(this);
    Attach(mView,ATTACH_REPLACECLIENT);
    mView->SetFocus();

    mMenu=new CMenu;
    mView->FillToolBar(mMenu);
    mToolBar=new CToolBar;
    Attach(mToolBar,gui::ATTACH_NORTH,gui::ATTACH_TOOLBAR);

    mView->Show();

    {
        python::CLock lPythonLock;

        mCodeModule=python::import_module("code");
        mCompileCommand=mCodeModule.GetAttribute("compile_command");

        YModule lSysModule("sys");
        CPythonStdout *lStdout=new CPythonStdout(this);
        lSysModule.SetAttribute("stdout",lStdout);
        lSysModule.SetAttribute("stderr",lStdout);

        CPythonHelp *lHelp=new CPythonHelp;
        YObject lPyHelp(lHelp);
        YObject lHelpFunction=lPyHelp.GetAttribute("help");
        python::get_builtins_module().SetAttribute("help",lHelpFunction);

        YModule lBuolabaseModule("buolabase");
        YObject lExitFunction=lBuolabaseModule.GetAttribute("exit");
        YObject lPauseFunction=lBuolabaseModule.GetAttribute("pause");
        python::get_builtins_module().SetAttribute("quit",lExitFunction);
        python::get_builtins_module().SetAttribute("pause",lPauseFunction);
        python::get_builtins_module().SetAttribute("exit",lExitFunction);
    }

    mIndentLevel=0;

    if(mStartupCommand.empty())
    {
        mView->Interact(L">>> ",0);
    }
    else
    {
        RunText(mStartupCommand,false);
    }

    mCtrlCAccel=new CAccelerator(KEY_C,KEYMOD_CONTROL,CAccelerator::LOCAL,this);
    mCtrlCAccel->eTriggered.Connect(&CPythonBox::OnCtrlC,this);
}

void CPythonBox::OnInteract(const std::wstring &pLine)
{
    python::CLock lPythonLock;

    mPendingCommand+=pLine;
    YObject lCode=mCompileCommand.Call(YObject(mPendingCommand));
    if(lCode.IsNull())
    {
        mIndentLevel=0;
        mPendingCommand.clear();
        PyErr_Print();
        PyErr_Clear();
        mView->Interact(L">>> ",0);
        mIndentLevel=0;
    }
    else if(lCode.IsNone())
    {
        if(pLine[pLine.length()-1]==':')
            mIndentLevel++;
        mPendingCommand+=L'\n';
        mView->Interact(L"... ",mIndentLevel);
    }
    else
    {
        mPendingCommand.clear();
        mRunning=true;
        mRunningThread=new CThread(lCode);
        mRunningThread->eThreadFinished.Connect(&CPythonBox::OnThreadFinished,this);
        mRunningThread->Start();
    }
}

void CPythonBox::OnEnterNotInteracting()
{
    PauseUntilInput(PAUSE_ENTER);
}

void CPythonBox::OnCtrlC()
{
    if(mRunning)
    {
        if(!PauseUntilInput(PAUSE_CTRLC))
            mRunningThread->RaiseInterrupt();
        mInterrupted=true;
    }
    else
    {
        mPendingCommand.clear();
        mView->CancelInteract();
        mView->Output(L"\n**Ctrl+C**\n");
        mView->Interact(L">>> ",0);
    }
}

void CPythonBox::OnThreadFinished()
{
    mView->Output(L"\n");
    mView->Interact(L">>> ");
    mIndentLevel=0;
    mInterrupted=false;
    mRunning=false;
}

/*namespace python */ } /*namespace buola*/ }
